package com.headcaselabs.s3;

import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.amazon.s3.Bucket;
import com.amazon.s3.ListAllMyBucketsResponse;
import com.amazon.s3.ListBucketResponse;
import com.amazon.s3.ListEntry;
import com.amazon.s3.S3StreamObject;

/**
 * Allows the retrieval of a Character Large Object via Amazon S3.
 * 
 * @author Florian Leibert
 * 
 */
public class DFSUtils {
	private String	awsAccessKeyId;

	private String	awsSecretAccessKey;


  private final static Log log_ =
    LogFactory.getLog(DFSUtils.class.getName());
	
	public DFSUtils(String keyId, String secretKey) {
		awsAccessKeyId = keyId;
		awsSecretAccessKey = secretKey;
	}

	// @FL TODO: find a way to properly release the connection ~ this is hard with
	// the given interface definition, because the GetMethod should be passed back
	// to the client application
	// and the connection should be released once the stream runs out of bytes...
	public InputStream getStream(String bucketName, String objectName)
			throws Exception {
		ConnectionUtilities conn = new ConnectionUtilities(awsAccessKeyId,
				awsSecretAccessKey);
		return conn.get(bucketName, objectName, null).getResponseBodyAsStream();
	}
	
	public boolean hasObject(String bucketName, String objectName) throws Exception {
		ConnectionUtilities conn = new ConnectionUtilities(awsAccessKeyId,
				awsSecretAccessKey);
		GetMethod gm = conn.get(bucketName, objectName, null);
		if (gm.getStatusCode()==200) 
			return true;
		else
			return false;		
	}

	@SuppressWarnings("unchecked")
	public Report putStream(String bucketName, String objectName, InputStream is,
			long contentLength) {
		// TODO: split bucket name in URL + BUCKET = URI
		StringBuffer sb = new StringBuffer();
		int i = 0;
		ConnectionUtilities conn = new ConnectionUtilities(awsAccessKeyId,
				awsSecretAccessKey);
		String encoding = "text/plain";
		Map headers;
		Map metadata;
		String date = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")
				.format(new Date());
		long start = System.currentTimeMillis();
		Report report = new Report();
		report.setDate(date);
		report.setUri(bucketName);
		report.setObjectName(objectName);
		report.setEncoding(encoding);
		int lastResponse = 200;
		try {
			boolean success = false;
			report.setUriExists(true);
			if (conn.getBucket(bucketName) == 404) {
				lastResponse = conn.createBucket(bucketName);
				report.setUriExists(false);
				if (lastResponse != 200) {
					sb.append("\n\tMessage " + i++
							+ ": bucket doesn't exist, creation failed with code:"
							+ lastResponse);
				} else {
					sb.append("\n\tMessage " + i++ + ": bucket successfully created:");
				}
			} else {
				sb.append("\n\tMessage " + i++ + ": using existing bucket :");
			}
			headers = new TreeMap();
			metadata = new TreeMap();
			headers.put("Content-Type", Arrays.asList(new String[] { encoding }));
			headers.put("x-amaz-meta-real-file-date", Arrays
					.asList(new String[] { date }));
			S3StreamObject object = new S3StreamObject(metadata);
			int response = conn.putStream(bucketName, objectName, object,
					contentLength, encoding, is, headers);
			sb.append("\n\tMessage " + i++ + ": Response code from stream upload, "
					+ response);
			report.setMetaData(object.toString());
			if (response == 200) {
				sb.append(" successfully stored.\n");
				success = true;
			}
			report.setStatusCode(response);
			report.setSuccess(success);
			long transferTime = System.currentTimeMillis() - start;
			report.setTransferTime(transferTime + " ms");
		} catch (Exception e) {
			e.printStackTrace();
			StringWriter sw = new StringWriter();
			e.printStackTrace(new PrintWriter(sw));
			report.setException(sw.getBuffer().toString());
			report.setSuccess(false);
		}
		report.setMessageStack(sb.toString());
		return report;
	}

	public Report deleteObject(String uri, String objectName) throws Exception {
		final ConnectionUtilities conn = new ConnectionUtilities(awsAccessKeyId,
				awsSecretAccessKey);
		HttpMethod hm = conn.deleteObject(uri, objectName);
		Report report = new Report();
		report.setStatusCode(hm.getStatusCode());
		if (hm.getStatusCode() >= 200 && hm.getStatusCode() < 300)
			report.setSuccess(true);
		return report;
	}

	/**
	 * Deletes a specific bucket
	 * 
	 * @param bucket
	 * @throws Exception
	 */
	public Report deleteBucket(String bucket) throws Exception {
		final ConnectionUtilities conn = new ConnectionUtilities(awsAccessKeyId,
				awsSecretAccessKey);
		HttpMethod hm = conn.deleteBucket(bucket, null);
		Report report = new Report();
		report.setStatusCode(hm.getStatusCode());
		if (hm.getStatusCode() == 200)
			report.setSuccess(true);
		return report;
	}

	/**
	 * Lists all available buckets
	 * 
	 * @return the List of available buckets
	 * @throws Exception
	 */
	public List<String> listBuckets() throws Exception {
		final ConnectionUtilities conn = new ConnectionUtilities(awsAccessKeyId,
				awsSecretAccessKey);
		ListAllMyBucketsResponse lbr = conn.listAllMyBuckets(null);
		LinkedList<String> buckets = new LinkedList<String>();
		for (Bucket b : lbr.entries) {
			buckets.add(b.name);
		}
		return buckets;
	}

	/**
	 * Lists the objects of a given bucket.
	 * 
	 * @param bucket
	 * @param prefix
	 * @param maxKeys
	 * @param marker
	 *          The first element to show
	 * @return a List of Strings containing the objects
	 * @throws Exception
	 */
	public List<String> listObjects(String bucket, String prefix,
			Integer maxKeys, String marker) throws Exception {
		final ConnectionUtilities conn = new ConnectionUtilities(awsAccessKeyId,
				awsSecretAccessKey);
		ListBucketResponse lbr = conn.listBucket(bucket, prefix, marker, maxKeys,
				null, null);
		LinkedList<String> entries = new LinkedList<String>();
		for (ListEntry le : lbr.entries) {
			entries.add(le.key);
		}
		return entries;
	}

}
